/*!	 synfig/rendering/software/function/contour.cpp
**	 Contour
**
**	......... ... 2015 Ivan Mahonin
**
**	This package is free software; you can redistribute it and/or
**	modify it under the terms of the GNU General Public License as
**	published by the Free Software Foundation; either version 2 of
**	the License, or (at your option) any later version.
**
**	This package is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**	General Public License for more details.
**
*/

#ifdef USING_PCH
#	include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#ifndef _WIN32
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#endif

#include "contour.h"

#include <synfig/debug/debugsurface.h>

#endif

using namespace synfig;
using namespace rendering;

void
software::Contour::render_polyspan(
    synfig::Surface &target_surface,
    const Polyspan &polyspan,
    bool invert,
    bool antialias,
    rendering::Contour::WindingStyle winding_style,
    const Color &color,
    Color::value_type opacity,
    Color::BlendMethod blend_method)
{
    bool simple_fill = (Color::BLEND_METHODS_OVERWRITE_ON_ALPHA_ONE & (1 << blend_method))
                       && fabsf(1.f - opacity * color.get_a()) <= 1e-6;

    synfig::Surface::alpha_pen p(target_surface.begin(), opacity, blend_method);
    synfig::Surface::pen sp(target_surface.begin());
    const RectInt &window = polyspan.get_window();
    const Polyspan::cover_array &covers = polyspan.get_covers();

    Polyspan::cover_array::const_iterator cur_mark = covers.begin();
    Polyspan::cover_array::const_iterator end_mark = covers.end();

    Real cover = 0, area = 0, alpha = 0;
    int	y = 0, x = 0;

    p.set_value(color);
    sp.set_value(color);
    cover = 0;

    if (cur_mark == end_mark) {
        // no marks at all
        if (invert) {
            if (simple_fill) {
                sp.move_to(window.minx, window.miny);
                sp.put_block(window.maxy - window.miny, window.maxx - window.minx);
            } else {
                p.move_to(window.minx, window.miny);
                p.put_block(window.maxy - window.miny, window.maxx - window.minx);
            }
        }

        return;
    }

    // fill initial rect / line
    if (invert) {
        if (simple_fill) {
            // fill all the area above the first vertex
            sp.move_to(window.minx, window.miny);
            y = window.miny;
            int l = window.maxx - window.minx;

            sp.put_block(cur_mark->y - window.miny, l);

            // fill the area to the left of the first vertex on that line
            l = cur_mark->x - window.minx;
            sp.move_to(window.minx, cur_mark->y);

            if (l) {
                sp.put_hline(l);
            }
        } else {
            // fill all the area above the first vertex
            p.move_to(window.minx, window.miny);
            y = window.miny;
            int l = window.maxx - window.minx;

            p.put_block(cur_mark->y - window.miny, l);

            // fill the area to the left of the first vertex on that line
            l = cur_mark->x - window.minx;
            p.move_to(window.minx, cur_mark->y);

            if (l) {
                p.put_hline(l);
            }
        }
    }

    while (true) {
        y = cur_mark->y;
        x = cur_mark->x;

        p.move_to(x, y);

        area = cur_mark->area;
        cover += cur_mark->cover;

        // accumulate for the current pixel
        while (++cur_mark != covers.end()) {
            if (y != cur_mark->y || x != cur_mark->x) {
                break;
            }

            area += cur_mark->area;
            cover += cur_mark->cover;
        }

        // draw pixel - based on covered area
        if (area) { // if we're ok, draw the current pixel
            alpha = polyspan.extract_alpha(cover - area, winding_style);

            if (invert) {
                alpha = 1 - alpha;
            }

            if (antialias) {
                if (alpha) {
                    p.put_value_alpha(alpha);
                }
            } else {
                if (alpha >= .5) {
                    p.put_value();
                }
            }

            p.inc_x();
            ++x;
        }

        // if we're done, don't use iterator and exit
        if (cur_mark == end_mark) {
            break;
        }

        // if there is no more live pixels on this line, goto next
        if (y != cur_mark->y) {
            if (invert) {
                // fill the area at the end of the line
                if (simple_fill) {
                    sp.move_to(p);
                    sp.put_hline(window.maxx - x);
                } else {
                    p.put_hline(window.maxx - x);
                }

                // fill area at the beginning of the next line
                if (simple_fill) {
                    sp.move_to(window.minx, cur_mark->y);
                    sp.put_hline(cur_mark->x - window.minx);
                } else {
                    p.move_to(window.minx, cur_mark->y);
                    p.put_hline(cur_mark->x - window.minx);
                }
            }

            cover = 0;
            continue;
        }

        // draw span to next pixel - based on total amount of pixel cover
        if (x < cur_mark->x) {
            alpha = polyspan.extract_alpha(cover, winding_style);

            if (invert) {
                alpha = 1 - alpha;
            }

            if (alpha >= .5) {
                if (simple_fill) {
                    sp.move_to(p);
                    sp.put_hline(cur_mark->x - x);
                    p.move_to(sp);
                } else {
                    p.put_hline(cur_mark->x - x);
                }
            }


        }
    }

    // fill the after stuff
    if (invert) {
        if (simple_fill) {
            sp.move_to(p);

            // fill the area at the end of the line
            sp.put_hline(window.maxx - x);

            // fill area at the beginning of the next line
            sp.move_to(window.minx, y + 1);
            sp.put_block(window.maxy - y - 1, window.maxx - window.minx);
        } else {
            // fill the area at the end of the line
            p.put_hline(window.maxx - x);

            // fill area at the beginning of the next line
            p.move_to(window.minx, y + 1);
            p.put_block(window.maxy - y - 1, window.maxx - window.minx);
        }
    }
}

void
software::Contour::build_polyspan(
    const rendering::Contour::ChunkList &chunks,
    const Matrix &transform_matrix,
    Polyspan &out_polyspan,
    Real detail)
{
    Vector p1, pp0, pp1;

    for (rendering::Contour::ChunkList::const_iterator i = chunks.begin(); i != chunks.end(); ++i) {
        switch (i->type) {
        case rendering::Contour::CLOSE:
            out_polyspan.close();
            break;

        case rendering::Contour::MOVE:
            p1 = transform_matrix.get_transformed(i->p1);
            out_polyspan.move_to(p1[0], p1[1]);
            break;

        case rendering::Contour::LINE:
            p1 = transform_matrix.get_transformed(i->p1);
            out_polyspan.line_to(p1[0], p1[1]);
            break;

        case rendering::Contour::CONIC:
            p1 = transform_matrix.get_transformed(i->p1);
            pp0 = transform_matrix.get_transformed(i->pp0);
            out_polyspan.conic_to(p1[0], p1[1], pp0[0], pp0[1], detail);
            break;

        case rendering::Contour::CUBIC:
            p1 = transform_matrix.get_transformed(i->p1);
            pp0 = transform_matrix.get_transformed(i->pp0);
            pp1 = transform_matrix.get_transformed(i->pp1);
            out_polyspan.cubic_to(p1[0], p1[1], pp0[0], pp0[1], pp1[0], pp1[1], detail);
            break;

        default:
            break;
        }
    }
}

void
software::Contour::render_contour(
    synfig::Surface &target_surface,
    const rendering::Contour::ChunkList &chunks,
    bool invert,
    bool antialias,
    rendering::Contour::WindingStyle winding_style,
    const Matrix &transform_matrix,
    const Color &color,
    Color::value_type opacity,
    Color::BlendMethod blend_method)
{
    Polyspan polyspan;
    polyspan.init(0, 0, target_surface.get_w(), target_surface.get_h());
    build_polyspan(chunks, transform_matrix, polyspan);
    polyspan.sort_marks();

    return render_polyspan(
               target_surface,
               polyspan,
               invert,
               antialias,
               winding_style,
               color,
               opacity,
               blend_method);
}